//
// Created by hugan on 2020/11/3.
//

#ifndef JOYSTICKINPUT_HROBOTJNI_CPP
#define JOYSTICKINPUT_HROBOTJNI_CPP

#include <jni.h>
#include <Windows.h>
#include "../hardware/VolumeUtil.h"
#include "../hardware/MouseUtil.h"
#include "../hardware/BatteryUtil.h"
#include "../common/ShellExecuteUtil.h"
#include "../common/CharsetConverter.h"

#ifdef __cplusplus
extern "C" {
#endif

#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }

/**
* 版本号
*/
JNIEXPORT jstring JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_version(JNIEnv *env, jclass clz) {
    return env->NewStringUTF("250121");
}

//鼠标,键盘控制======
/**
* 移动鼠标
* @note 移动失败的情况:任务管理器,管理员运行的[微信,jar程序];(管理员权限运行时可以成功控制)
*/
JNIEXPORT jboolean JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_mouseMove(JNIEnv *env, jclass clz, jint x, jint y) {
    return MouseUtil::move(x, y);
}

/**
* 模拟滚轮滚动
* @param value 滚动n个单位的距离,取值非零整数:小于0表示向上/左
* @param isHorizontal false-竖直滚动;true-水平滚动
*/
JNIEXPORT jboolean JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_mouseWheel(JNIEnv *env, jclass clz, jint value, jboolean isHorizontal) {
    return MouseUtil::wheel(value, isHorizontal);
}

/**
* 显示鼠标; 解决开机/重新登陆时,不移动物理鼠标时,系统不显示鼠标光标
* @note 失败的情况(方法仍会返回成功):任务管理器,管理员运行的[微信,jar程序];(管理员权限运行时可以成功控制)
*/
JNIEXPORT jboolean JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_showMouse(JNIEnv *env, jclass clz) {
    return MouseUtil::showMouse();
}

/**
* 切换媒体[播放/暂停](能控制谷歌浏览器);
 * .使用激活虚拟按键的方式
*/
JNIEXPORT void JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_mediaPlayPause(JNIEnv *env, jclass clz) {
    WORD key = VK_MEDIA_PLAY_PAUSE;
    INPUT inputs[2];
    inputs[0].type = INPUT_KEYBOARD;
    inputs[0].ki = {key, 0, 0, 0, 0};
    inputs[1].type = INPUT_KEYBOARD;
    inputs[1].ki = {key, 0, KEYEVENTF_KEYUP, 0, 0};
    SendInput(2, inputs, sizeof(INPUT));
}

/**
 * 模拟按键
 * @param keyCode 来源[winuser.h], 如[VK_MEDIA_STOP]的值
*/
JNIEXPORT jint JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_clickKey(JNIEnv *env, jclass clz, jint keyCode) {
    WORD key = static_cast<WORD>(keyCode);
    INPUT inputs[2];
    inputs[0].type = INPUT_KEYBOARD;
    inputs[0].ki = {key, 0, 0, 0, 0};
    inputs[1].type = INPUT_KEYBOARD;
    inputs[1].ki = {key, 0, KEYEVENTF_KEYUP, 0, 0};
    return SendInput(2, inputs, sizeof(INPUT));
}

/**
 * 获取键盘灯开关状态
 * @return byte[00000111],右边n位分别代表[VK_SCROLL,VK_CAPITAL,VK_NUMLOCK]的开关状态
 */
JNIEXPORT jbyte JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_getKeyboardState(JNIEnv *env, jclass clz) {
    BYTE keyState[256];
    GetKeyboardState((LPBYTE) &keyState);
    bool isOnNum = GetKeyState(VK_NUMLOCK) & 0x0001;
    bool isOnCaps = GetKeyState(VK_CAPITAL) & 0x0001;
    bool isOnSCROLL = GetKeyState(VK_SCROLL) & 0x0001;
    return (byte) (((isOnSCROLL ? 1 : 0) << 2) | ((isOnCaps ? 1 : 0) << 1) | (isOnNum ? 1 : 0));
}

//系统状态======
/**
 * 锁屏
 * @note 无管理员权限问题
 */
JNIEXPORT jboolean JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_lockWorkStation(JNIEnv *env, jclass clz) {
    return LockWorkStation();
}

//========VolumeUtil
VolumeUtil *gpVolumeUtil;

/**
 * 创建全局变量VolumeUtil;此方法可以重复调用
 * 目的:VolumeUtil[创建,销毁]时会涉及一些资源和线程操作;
 * 如果是连续频繁调用(如滑动调节音量),则建议[createGlobalVolumeUtil,releaseGlobalVolumeUtil];
 * 否则每次调用方法时,都是即时[创建和销毁资源]的
 */
JNIEXPORT void JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_createGlobalVolumeUtil(JNIEnv *env, jclass clz) {
    if (!gpVolumeUtil) {
//        std::cout << "createVolumeUtil" << std::endl;
        gpVolumeUtil = new VolumeUtil();
    }
}

/**
 * 释放全局变量VolumeUtil;此方法可以重复调用
 */
JNIEXPORT void JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_releaseGlobalVolumeUtil(JNIEnv *env, jclass clz) {
    if (gpVolumeUtil) {
//        std::cout << "releaseVolumeUtil" << std::endl;
        (gpVolumeUtil)->release();
        (gpVolumeUtil) = nullptr;
    }
}

/**
 * 查询系统是否静音
 */
JNIEXPORT jboolean JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_isMute(JNIEnv *env, jclass clz) {
    if (gpVolumeUtil) {
        return gpVolumeUtil->isMute();
    } else {
        VolumeUtil volumeUtil;
        return volumeUtil.isMute();
    }
}

/**
 * 设置系统静音状态
 * @note 系统不会弹出音量提示图案
 */
JNIEXPORT void JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_setMute(JNIEnv *env, jclass clz, jboolean v) {
    if (gpVolumeUtil) {
        gpVolumeUtil->setMute(v == JNI_TRUE);
    } else {
        VolumeUtil volumeUtil;
        volumeUtil.setMute(v == JNI_TRUE);
    }
}

/**
 * 获取系统音量
 * @return 范围[0,100]
 */
JNIEXPORT jint JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_getVolume(JNIEnv *env, jclass clz) {
    if (gpVolumeUtil) {
        return gpVolumeUtil->getVolume();
    } else {
        VolumeUtil volumeUtil;
        return volumeUtil.getVolume();
    }
}

/**
 * 设置系统音量,会解除静音状态
 * @param v 范围[0,100],超出范围时会自动靠拢
 * @note 系统不会弹出音量提示图案
 * @return 被设置的音量
 */
JNIEXPORT jint JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_setVolume(JNIEnv *env, jclass clz, jint v) {
    if (gpVolumeUtil) {
        return gpVolumeUtil->setVolume(v);
    } else {
        VolumeUtil volumeUtil;
        return volumeUtil.setVolume(v);
    }
}

//========BatteryUtil
/**
 * 查询是否连接充电器
 * @return 0-否,1-是,255-未知
 */
JNIEXPORT jint JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_isAcOnline(JNIEnv *env, jclass clz) {
    return BatteryUtil::isAcOnline();
}

/**
 * 获取电量值
 * @return [0~100]-正常值,255-未知
 */
JNIEXPORT jint JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_getBatteryValue(JNIEnv *env, jclass clz) {
    return BatteryUtil::getBatteryValue();
}

//========ShellExecute
/**
 * ShellExecute的[open]指令,用于[运行程序,用默认浏览器打开网页,打开文件];
 * 目的是代替[java.awt.Desktop#open]无法设置[运行目录]的问题;
 * @param filePath 要打开/执行的文件路径
 * @param runDirectory 运行目录;nullable-在当前目录运行指令
 * @note 原则上[运行程序]时,都应该传入[程序的父目录],无奈cpp在获取父目录时十分无语,所以runDirectory都在java层处理;
 * 打开网页时,如果是新启动的浏览器,则界面缩放比例会错误(不是运行路径问题),暂无解决方法;
 * 获取当前默认浏览器路径 https://blog.csdn.net/ityezi_admin/article/details/26565425
 */
JNIEXPORT void JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_shellExecuteOpen(JNIEnv *env, jclass clz,
                                                               jstring filePath, jstring runDirectory,
                                                               jstring param) {
    std::string ansiFile;
    CharsetConverter::jString2ansi(env, filePath, ansiFile);
    std::string ansiDir;
    if (runDirectory) CharsetConverter::jString2ansi(env, runDirectory, ansiDir);
    std::string ansiParam;
    if (param) CharsetConverter::jString2ansi(env, param, ansiParam);
    ShellExecuteUtil::open(ansiFile.c_str(),
                           ansiDir.empty() ? nullptr : ansiDir.c_str(),
                           ansiParam.empty() ? nullptr : ansiParam.c_str());
}

//========窗口功能
/**
* 反截屏/截屏时[隐藏,黑屏]窗口
* https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-setwindowdisplayaffinity
*
* @param hWnd 必须是setVisible=true,且后续无调用过dispose;只要符合前者,setVisible=false不会影响
* @param dwAffinity WDA_NONE,WDA_MONITOR,WDA_EXCLUDEFROMCAPTURE
* @apiNote 仅支持[本程序的窗口, 常规方式截屏]; win10以下有兼容问题,详见微软文档
* @apiNote 使用注意!! [黑窗]和[隐藏]时不能直接[互换],必须先切换成[正常]再切换[新类型]; 中途dispose无影响
*/
JNIEXPORT jboolean JNICALL
Java_com_hup_utils_commons_hardware_HRobotJni_setWindowDisplayAffinity(JNIEnv *env, jclass clz,
                                                                       jlong hWnd, jint dwAffinity) {
    return SetWindowDisplayAffinity((HWND) (uintptr_t) hWnd, (DWORD) dwAffinity);
}

#ifdef __cplusplus
}
#endif
#endif //JOYSTICKINPUT_HROBOTJNI_CPP
